package com.qianheng.kit.base.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.*;

@Slf4j
public class ObjectModifyUtil {

    /**
     * 对象字段递归修改
     * 只是帮助递归，使用getter， setter方法
     * 不会生成新对象
     *
     * @param obj              需要修改的对象
     * @param modifyFunction   字段修改策略
     * @param isRecurrenceType 类型是否允许递归。默认不会递归到”java“开头的全类名
     * @param logSb            执行日志
     * @param n                递归深度
     */
    public static void deepModify(Object obj, ModifyFunction<Object> modifyFunction, Predicate<Class<?>> isRecurrenceType, StringBuilder logSb, int n) {
        try {
            recurrenceModify(obj, modifyFunction, isRecurrenceType, logSb, n);
        } catch (Throwable e) {
            log.warn("deepModify ,objName={}, log={}, n={}", obj.getClass().getName(), logSb, n, e);
        }
    }


    private static int recurrenceModify(Object obj, ModifyFunction<Object> modifyFunction, Predicate<Class<?>> isRecurrenceType, StringBuilder logSb, int n) throws InvocationTargetException, IllegalAccessException {
        n--;
        if (n < 0) {
            return -1;
        }
        if (obj == null) {
            return n;
        }
        logSb.append("\"").append(obj.getClass().getSimpleName())
                .append("|").append(n)
                .append("\":").append("{");

        //全部get方法
        Set<Method> getterMethods = ReflectionUtils.getAllMethods(obj.getClass(), method -> method.getName().startsWith("get") && method.getParameterCount() == 0);
        for (Method getterMethod : getterMethods) {
            if (!getterMethod.getName().startsWith("get")) {
                continue;
            }
            Object getterObj = getterMethod.invoke(obj, null);
            if (getterObj == null) {
                continue;
            }
            if (getterObj instanceof Collection) {
                Collection c = (Collection) getterObj;
                Object first = c.stream().findFirst().orElse(null);
                if (!isRecurrenceType(first, isRecurrenceType)) {
                    continue;
                }
                for (Object o1 : c) {
                    recurrenceModify(o1, modifyFunction, isRecurrenceType, logSb, n);
                }
            } else if (getterObj instanceof Map) {
                Map m = (Map) getterObj;
                for (Object key : m.keySet()) {
                    Object value = m.get(key);
                    if (value instanceof CharSequence || value instanceof Number) {
                        doOneMapPut(key, value, m, modifyFunction, logSb);
                    }
                    recurrenceModify(value, modifyFunction, isRecurrenceType, logSb, n);
                }
            } else if (isRecurrenceType(getterObj, isRecurrenceType)) {
                recurrenceModify(getterObj, modifyFunction, isRecurrenceType, logSb, n);
            } else {
                doOneObjModify(obj, getterMethod, getterObj, modifyFunction, logSb);
            }
        }

        logSb.append("}");
        return n;
    }

    /**
     * 一个字段脱敏
     *
     * @param obj            主体对象
     * @param getterMethod   obj的一个get方法
     * @param getterObj      该get方法的返回对象
     * @param modifyFunction 脱敏配置 BiFunction<父对象, 当前操作对象,返回值>
     * @param logSb          日志
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private static void doOneObjModify(Object obj, Method getterMethod, Object getterObj, ModifyFunction<Object> modifyFunction, StringBuilder logSb) {
        if (getterMethod == null) {
            return;
        }

        String fieldName = lowerFirst(StringUtils.replace(getterMethod.getName(), "get", "", 1));
        String setterName = StringUtils.replace(getterMethod.getName(), "g", "s", 1);
        Method setterMethod = null;
        try {
            setterMethod = obj.getClass().getMethod(setterName, getterObj.getClass());
        } catch (Exception e) {
            logOne(logSb, fieldName, "no setter method");
            return;
        }
        Object apply = modifyFunction.apply(getterObj, fieldName);
        if (Objects.equals(apply, getterObj)) {
            logOne(logSb, fieldName, "notChange");
            return;
        }

        //执行
        try {
            setterMethod.invoke(obj, apply);
        } catch (Exception e) {
            logOne(logSb, fieldName, "setter exception");
        }
        //日志
        logOne(logSb, fieldName, "modifyed");
    }


    /**
     * 处理map的一个put请求
     *
     * @param key
     * @param value
     * @param m
     * @param modifyFunction
     * @param logSb
     */
    private static void doOneMapPut(Object key, Object value, Map m, ModifyFunction<Object> modifyFunction, StringBuilder logSb) {
        if (key == null || m == null) {
            return;
        }
        String fieldName = String.valueOf(key);
        Object apply = modifyFunction.apply(value, fieldName);
        if (Objects.equals(apply, value)) {
            logOne(logSb, "Map." + fieldName, "notChange");
            return;
        }
        m.put(key, apply);
        logOne(logSb, "Map." + fieldName, "modifyed");
    }

    /**
     * 类型白名单
     */
    public static boolean isRecurrenceType(Object obj, Predicate<Class<?>> isRecurrenceType) {
        if (obj == null) {
            return false;
        }
        Class<?> aClass = obj.getClass();
        if (aClass.getName().startsWith("java")) {
            return false;
        }
        return isRecurrenceType.test(aClass);
    }

    private static void logOne(StringBuilder logSb, String fieldName, String modifyMsg) {
        logSb.append("\"").append(fieldName).append("\"")
                .append(":")
                .append("\"").append(modifyMsg).append("\",");
    }

    private static String lowerFirst(String s) {
        return s != null && s.length() != 0 ? Character.toLowerCase(s.charAt(0)) + s.substring(1) : s;
    }

    private static String upperFirst(String s) {
        return s != null && s.length() != 0 ? Character.toUpperCase(s.charAt(0)) + s.substring(1) : s;
    }

    @FunctionalInterface
    public interface ModifyFunction<C> {
        /**
         * @param fieldObj  要修改的字段对象
         * @param fieldName 字段名称
         * @return
         */
        C apply(C fieldObj, String fieldName);
    }

}
